/*!
 * An jQuery | zepto plugin for lazy loading images.
 * author -> jieyou
 * see https://github.com/jieyou/lazyload
 * use some tuupola's code https://github.com/tuupola/jquery_lazyload (BSD)
 * use component's throttle https://github.com/component/throttle (MIT)
 */
;(function (factory) {
  if (typeof define === 'function' && define.amd) {
    // AMD
    // you may need to change `define([------>'jquery'<------], factory)`
    // if you use zepto, change it rely name, such as `define(['zepto'], factory)`
    define(['jquery'], factory)
    // define(['zepto'], factory)
  } else {
    // Global
    factory(window.jQuery || window.Zepto)
  }
})(function ($, undefined) {
  var w = window,
    $window = $(w),
    defaultOptions = {
      threshold: 0,
      failure_limit: 0,
      event: 'scroll',
      effect: 'show',
      effect_params: null,
      container: w,
      data_attribute: 'original',
      data_srcset_attribute: 'original-srcset',
      skip_invisible: true,
      appear: emptyFn,
      load: emptyFn,
      vertical_only: false,
      check_appear_throttle_time: 300,
      url_rewriter_fn: emptyFn,
      no_fake_img_loader: false,
      placeholder_data_img:
        '',
      // for IE6\7 that does not support data image
      placeholder_real_img: 'http://ditu.baidu.cn/yyfm/lazyload/0.0.1/img/placeholder.png'
      // todo : 将某些属性用global来配置，而不是每次在$(selector).lazyload({})内配置
    },
    type // function

  function emptyFn() {}

  type = (function () {
    var object_prototype_toString = Object.prototype.toString
    return function (obj) {
      // todo: compare the speeds of replace string twice or replace a regExp
      return object_prototype_toString.call(obj).replace('[object ', '').replace(']', '')
    }
  })()

  function belowthefold($element, options) {
    var fold
    if (options._$container == $window) {
      fold = ('innerHeight' in w ? w.innerHeight : $window.height()) + $window.scrollTop()
    } else {
      fold = options._$container.offset().top + options._$container.height()
    }
    return fold <= $element.offset().top - options.threshold
  }

  function rightoffold($element, options) {
    var fold
    if (options._$container == $window) {
      // Zepto do not support `$window.scrollLeft()` yet.
      fold = $window.width() + ($.fn.scrollLeft ? $window.scrollLeft() : w.pageXOffset)
    } else {
      fold = options._$container.offset().left + options._$container.width()
    }
    return fold <= $element.offset().left - options.threshold
  }

  function abovethetop($element, options) {
    var fold
    if (options._$container == $window) {
      fold = $window.scrollTop()
    } else {
      fold = options._$container.offset().top
    }
    // console.log('abovethetop fold '+ fold)
    // console.log('abovethetop $element.height() '+ $element.height())
    return fold >= $element.offset().top + options.threshold + $element.height()
  }

  function leftofbegin($element, options) {
    var fold
    if (options._$container == $window) {
      // Zepto do not support `$window.scrollLeft()` yet.
      fold = $.fn.scrollLeft ? $window.scrollLeft() : w.pageXOffset
    } else {
      fold = options._$container.offset().left
    }
    return fold >= $element.offset().left + options.threshold + $element.width()
  }

  function checkAppear($elements, options) {
    var counter = 0
    $elements.each(function (i, e) {
      var $element = $elements.eq(i)
      if ($element.css('display') === 'none') {
        return
      }
      function appear() {
        $element.trigger('_lazyload_appear')
        // if we found an image we'll load, reset the counter
        counter = 0
      }
      // If vertical_only is set to true, only check the vertical to decide appear or not
      // In most situations, page can only scroll vertically, set vertical_only to true will improve performance
      if (options.vertical_only) {
        if (abovethetop($element, options)) {
          // Nothing.
        } else if (!belowthefold($element, options)) {
          appear()
        } else {
          if (++counter > options.failure_limit) {
            return false
          }
        }
      } else {
        if (abovethetop($element, options) || leftofbegin($element, options)) {
          // Nothing.
        } else if (!belowthefold($element, options) && !rightoffold($element, options)) {
          appear()
        } else {
          if (++counter > options.failure_limit) {
            return false
          }
        }
      }
    })
  }

  // Remove image from array so it is not looped next time.
  function getUnloadElements($elements) {
    return $elements.filter(function (i) {
      return !$elements.eq(i).data('_lazyload_loadStarted')
    })
  }

  // throttle : https://github.com/component/throttle , MIT License
  function throttle(func, wait) {
    var ctx, args, rtn, timeoutID // caching
    var last = 0

    return function throttled() {
      ctx = this
      args = arguments
      var delta = new Date() - last
      if (!timeoutID)
        if (delta >= wait) call()
        else timeoutID = setTimeout(call, wait - delta)
      return rtn
    }

    function call() {
      timeoutID = 0
      last = +new Date()
      rtn = func.apply(ctx, args)
      ctx = null
      args = null
    }
  }

  if (!$.fn.hasOwnProperty('lazyload')) {
    $.fn.lazyload = function (options) {
      var $elements = this,
        isScrollEvent,
        isScrollTypeEvent,
        throttleCheckAppear

      if (!$.isPlainObject(options)) {
        options = {}
      }

      $.each(defaultOptions, function (k, v) {
        var typeK = type(options[k])
        if ($.inArray(k, ['threshold', 'failure_limit', 'check_appear_throttle_time']) != -1) {
          // these params can be a string
          if (typeK == 'String') {
            options[k] = parseInt(options[k], 10)
          } else if (typeK != 'Number') {
            options[k] = v
          }
        } else if (k == 'container') {
          // options.container can be a seletor string \ dom \ jQuery object
          if (options.hasOwnProperty(k)) {
            if (options[k] == w || options[k] == document) {
              options._$container = $window
            } else {
              options._$container = $(options[k])
            }
          } else {
            options._$container = $window
          }
          delete options.container
        } else if (defaultOptions.hasOwnProperty(k) && (!options.hasOwnProperty(k) || typeK != type(defaultOptions[k]))) {
          options[k] = v
        }
      })

      isScrollEvent = options.event == 'scroll'
      throttleCheckAppear = options.check_appear_throttle_time == 0 ? checkAppear : throttle(checkAppear, options.check_appear_throttle_time)

      // isScrollTypeEvent cantains custom scrollEvent . Such as 'scrollstart' & 'scrollstop'
      // https://github.com/search?utf8=%E2%9C%93&q=scrollstart
      isScrollTypeEvent = isScrollEvent || options.event == 'scrollstart' || options.event == 'scrollstop'

      $elements.each(function (i, e) {
        var element = this,
          $element = $elements.eq(i),
          placeholderSrc = $element.attr('src'),
          originalSrcInAttr = $element.attr('data-' + options.data_attribute), // `data-original` attribute value
          originalSrc = options.url_rewriter_fn == emptyFn ? originalSrcInAttr : options.url_rewriter_fn.call(element, $element, originalSrcInAttr),
          originalSrcset = $element.attr('data-' + options.data_srcset_attribute),
          isImg = $element.is('img')

        if ($element.data('_lazyload_loadStarted') || placeholderSrc == originalSrc) {
          $element.data('_lazyload_loadStarted', true)
          $elements = getUnloadElements($elements)
          return
        }

        $element.data('_lazyload_loadStarted', false)

        // If element is an img and no src attribute given, use placeholder.
        if (isImg && !placeholderSrc) {
          // For browsers that do not support data image.
          $element
            .one('error', function () {
              // `on` -> `one` : IE6 triggered twice error event sometimes
              $element.attr('src', options.placeholder_real_img)
            })
            .attr('src', options.placeholder_data_img)
        }

        // When appear is triggered load original image.
        $element.one('_lazyload_appear', function () {
          var effectParamsIsArray = $.isArray(options.effect_params),
            effectIsNotImmediacyShow
          function loadFunc() {
            // In most situations, the effect is immediacy show, at this time there is no need to hide element first
            // Hide this element may cause css reflow, call it as less as possible
            if (effectIsNotImmediacyShow) {
              // todo: opacity:0 for fadeIn effect
              $element.hide()
            }
            if (isImg) {
              // attr srcset first
              if (originalSrcset) {
                $element.attr('srcset', originalSrcset)
              }
              if (originalSrc) {
                $element.attr('src', originalSrc)
              }
            } else {
              $element.css('background-image', 'url("' + originalSrc + '")')
            }
            if (effectIsNotImmediacyShow) {
              $element[options.effect].apply($element, effectParamsIsArray ? options.effect_params : [])
            }
            $elements = getUnloadElements($elements)
          }
          if (!$element.data('_lazyload_loadStarted')) {
            effectIsNotImmediacyShow = options.effect != 'show' && $.fn[options.effect] && (!options.effect_params || (effectParamsIsArray && options.effect_params.length == 0))
            if (options.appear != emptyFn) {
              options.appear.call(element, $element, $elements.length, options)
            }
            $element.data('_lazyload_loadStarted', true)
            if (options.no_fake_img_loader || originalSrcset) {
              if (options.load != emptyFn) {
                $element.one('load', function () {
                  options.load.call(element, $element, $elements.length, options)
                })
              }
              loadFunc()
            } else {
              $('<img />')
                .one('load', function () {
                  // `on` -> `one` : IE6 triggered twice load event sometimes
                  loadFunc()
                  if (options.load != emptyFn) {
                    options.load.call(element, $element, $elements.length, options)
                  }
                })
                .attr('src', originalSrc)
            }
          }
        })

        // When wanted event is triggered load original image
        // by triggering appear.
        if (!isScrollTypeEvent) {
          $element.on(options.event, function () {
            if (!$element.data('_lazyload_loadStarted')) {
              $element.trigger('_lazyload_appear')
            }
          })
        }
      })

      // Fire one scroll event per scroll. Not one scroll event per image.
      if (isScrollTypeEvent) {
        options._$container.on(options.event, function () {
          throttleCheckAppear($elements, options)
        })
      }

      // Check if something appears when window is resized.
      // Force initial check if images should appear when window is onload.
      $window.on('resize load', function () {
        throttleCheckAppear($elements, options)
      })

      // Force initial check if images should appear.
      $(function () {
        throttleCheckAppear($elements, options)
      })

      return this
    }
  }
})
